package org.budo.graph.view.util;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.lang3.math.NumberUtils;
import org.budo.dubbo.Dubbo;
import org.budo.dubbo.config.builder.ReferenceBuilder;
import org.budo.graph.Constant;
import org.budo.graph.entity.DubboProvider;
import org.budo.graph.service.BudoGraphService;
import org.budo.graph.service.impl.HttpRemoteGraphServiceImpl;
import org.budo.graph.service.impl.SpringBudoGraphServiceImpl;
import org.budo.graph.service.util.FileExplorerUtil;
import org.budo.support.apache.commons.fileupload.servlet.util.ServletFileUploadUtil;
import org.budo.support.apache.commons.fileupload.util.FileItemUtil;
import org.budo.support.application.registry.BudoApplication;
import org.budo.support.application.registry.util.BudoApplicationRegistryUtil;
import org.budo.support.freemarker.FreemarkerUtil;
import org.budo.support.java.net.util.JavaNetUtil;
import org.budo.support.java.net.util.UrlEncodeUtil;
import org.budo.support.lang.util.FileUtil;
import org.budo.support.lang.util.IoUtil;
import org.budo.support.lang.util.ListUtil;
import org.budo.support.lang.util.MapUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.servlet.util.QueryStringUtil;
import org.budo.support.servlet.util.RequestUtil;
import org.budo.support.servlet.util.ResponseUtil;
import org.budo.support.slf4j.Slf4j;
import org.budo.support.spring.io.util.ResourceUtil;
import org.budo.support.spring.util.SpringUtil;
import org.slf4j.Logger;
import org.springframework.context.ApplicationContext;

import com.alibaba.druid.support.json.JSONUtils;

/**
 * @author limingwei
 * @date 2016年6月7日 上午9:40:19
 */
public class BudoGraphViewUtil {
    private static final Logger log = Slf4j.getLogger();

    private static final int REFERENCE_TIMEOUT = 9 * 1000;

    private static final String DUBBO_DRUID_FM_DUBBO_DRUID_INDEX = Constant.GRAPH_TEMPLATE_PATH + "/budo-graph-index.htm";

    private static final String DUBBO_DRUID_FM_DUBBO_DRUID_INDEX_VUE = Constant.GRAPH_TEMPLATE_PATH + "/budo-graph-index-vue.htm";

    private static final String[] HTML_APIS = new String[] { "/system-property", "/graph-data", "/log-level", "/file-explorer", "/dubbo-echo", //
            "/dubbo-service", "/placeholder", "/system-info", "/class-path", "/git-log", "/active-connections", "/top-thread", //
            "/top-process", "/perf-count", "/memory-map", "/thread-info", "/database-metadata", //
            "/spring-bean", "/ehcache-statistics", "/redis-pool", "/available-fonts", "/sql-request", "/method-request", "/session-request", "/mybatis-mapper", "/spring-task" };

    /**
     * isHtmlApi
     */
    public static boolean isHtmlApi(String requestURI) {
        if (requestURI.endsWith(".api")) {
            return true;
        }

        for (String each : HTML_APIS) {
            if (StringUtil.contains(requestURI, each)) {
                return true;
            }
        }

        return false;
    }

    public static void writeIndexResponse_VUE(ApplicationContext applicationContext, HttpServletRequest request, HttpServletResponse response) {
        response.setContentType(Constant.ContentType.TEXT_HTML);

        String inputString = ResourceUtil.classPathResourceInputStreamToString(DUBBO_DRUID_FM_DUBBO_DRUID_INDEX_VUE);
        IoUtil.write(inputString, ResponseUtil.getOutputStream(response));
        ResponseUtil.flushBuffer(response);
    }

    private static String getIp(HttpServletRequest request) {
        String value = request.getParameter("host");
        if (StringUtil.isNotBlank(value)) {
            return value;
        }

        String requestURI = request.getRequestURI();

        String uri = requestURI.substring(request.getContextPath().length() + 2);
        int a = uri.indexOf('/');
        int b = uri.indexOf('/', a + 1);

        if (b > a + 1) {
            return uri.substring(a + 1, b);
        }

        return null;
    }

    private static Integer getPort(HttpServletRequest request) {
        String value = request.getParameter("port");
        if (StringUtil.isNotBlank(value)) {
            return NumberUtils.toInt(value);
        }

        String requestURI = request.getRequestURI();
        if (requestURI.endsWith("login.html")) {
            return null;
        }

        String contextPath = request.getContextPath();
        String uri = requestURI.substring(contextPath.length() + 1); // 去掉 contextPath

        int a = uri.indexOf('/');
        int b = uri.indexOf('/', a + 1);
        int c = uri.indexOf('/', b + 1);

        if (c > b + 1) {
            String port = uri.substring(b + 1, c);
            for (char ch : port.toCharArray()) {
                if (!Character.isDigit(ch)) {
                    log.debug("#90 getPort() error 1, return null, requestURI={}, contextPath={}, port={}, uri={}, a={}, b={}, c={}", requestURI, contextPath, port, uri, a, b, c);
                    return null;
                }
            }

            return Integer.valueOf(port);
        }

        return null;
    }

    private static String getApi(HttpServletRequest request) {
        String requestURI = request.getRequestURI();

        String uri = requestURI.substring(request.getContextPath().length() + 2);
        int a = uri.indexOf('/');
        int b = uri.indexOf('/', a + 1);
        int c = uri.indexOf('/', b + 1);

        if (c < 0) {
            return null;
        }

        return uri.substring(c);
    }

    public static String writeApiResponse(String host, Integer port, String apiUrl) {
        BudoGraphService budoGraphService = buildBudoGraphService(host, port, null);
        String serviceDubboDruidStat = budoGraphService.serviceDubboDruidStat(apiUrl);
        return new String(serviceDubboDruidStat.getBytes());
    }

    public static void writeApiResponse(HttpServletRequest request, HttpServletResponse response, ApplicationContext applicationContext) {
        String ip = getIp(request);
        Integer port = getPort(request);
        String apiUrl = getApi(request);

        String queryString = RequestUtil.getQueryString(request);
        if (null != queryString && !queryString.trim().isEmpty()) {
            apiUrl += "?" + queryString;
        }

        Map<String, String> parameters = QueryStringUtil.queryStringToStringMap(apiUrl);

        // 如果是download添加文件下载 header
        writeHeaderIfFileDownload(response, apiUrl, parameters);

        // 返回输出流
        InputStream inputStream = null;
        if (StringUtil.startsWith(apiUrl, "/file-explorer") // 是文件
                && JavaNetUtil.getNetworkInterfaceAddresses().contains(ip)) { // 是本机文件
            inputStream = handleLocalFileRequest(request, applicationContext, apiUrl, ip, port, parameters);
        } else { // 不是文件，或不是本机，走dubbo
            BudoGraphService budoGraphService = buildBudoGraphService(ip, port, applicationContext);
            String serviceDubboDruidStat = budoGraphService.serviceDubboDruidStat(apiUrl);
            inputStream = new ByteArrayInputStream(serviceDubboDruidStat.getBytes());
        }

        // 写内容到response
        ServletOutputStream outputStream = ResponseUtil.getOutputStream(response);
        IoUtil.write(inputStream, outputStream, true, false);
        ResponseUtil.flushBuffer(response);
    }

    private static void writeHeaderIfFileDownload(HttpServletResponse response, String api, Map<String, String> parameters) {
        if (!StringUtil.startsWith(api, "/file-explorer")) {
            return;
        }

        if (!StringUtil.equalsIgnoreCase("true", parameters.get("download"))) {
            return;
        }

        String path = parameters.get("_");
        String filename = path.substring(path.lastIndexOf(File.separatorChar) + 1);
        String encode = UrlEncodeUtil.encode(filename, "UTF-8");
        response.addHeader("Content-Disposition", "attachment; filename=\"" + encode + "\"");
    }

    private static InputStream handleLocalFileRequest(HttpServletRequest request, ApplicationContext applicationContext, String api, String ip, int port, Map<String, String> parameters) {
        if (StringUtil.equalsIgnoreCase("true", parameters.get("download"))) { // 是本机文件下载请求
            String path = parameters.get("_");
            return IoUtil.fileInputStream(new File(path)); // 读取本地文件
        }

        if (StringUtil.equalsIgnoreCase("true", parameters.get("mkdir"))) { // 本机创建文件夹
            String parent = parameters.get("parent");
            String dir = request.getParameter("dir");

            File parentFile = new File(parent);
            File dirFile = new File(parentFile, dir);
            dirFile.mkdirs();

            return new ByteArrayInputStream(("创建文件夹" + parent + File.separator + dir + "成功").getBytes());
        }

        if (StringUtil.equalsIgnoreCase("true", parameters.get("upload"))) { // 本机上传文件
            String parent = parameters.get("parent");
            File parentFile = new File(parent);

            List<FileItem> fileItems = ServletFileUploadUtil.parseRequest(request);
            for (FileItem fileItem : fileItems) {
                if (!fileItem.isFormField()) {
                    File toFile = new File(parentFile, fileItem.getName());
                    if (toFile.exists()) {
                        return new ByteArrayInputStream(("文件" + FileUtil.getCanonicalPath(toFile) + "已存在").getBytes());
                    }
                    if (!toFile.canWrite()) {
                        return new ByteArrayInputStream(("文件" + FileUtil.getCanonicalPath(toFile) + "不可写").getBytes());
                    }
                    FileItemUtil.write(fileItem, toFile);
                }
            }

            return new ByteArrayInputStream(("上传文件到" + parent + "成功").getBytes());
        }

        String afterFileIndex = "";
        String path = parameters.get("_");
        if (null != path && !path.trim().isEmpty()) {
            File file = new File(path);
            if (file.exists() && file.isDirectory()) {
                afterFileIndex += "<form style=\"margin:0px;display: inline\" method=\"POST\" " //
                        + "action=\"file-explorer?mkdir=true&parent=" + FileUtil.getCanonicalPath(file) + "\" " //
                        + "target=\"_blank\">" //
                        + "<input name=\"dir\"/>" //
                        + "<input type=\"submit\" value=\"mkdir\"/>" //
                        + "</form>";
                afterFileIndex += "<form style=\"margin:0px;display: inline\" method=\"POST\" " //
                        + " action=\"file-explorer?upload=true&parent=" + FileUtil.getCanonicalPath(file) + "\" " //
                        + " target=\"_blank\" " //
                        + " enctype=\"multipart/form-data\">" //
                        + "<input type=\"file\" name=\"file\"/>" //
                        + "<input type=\"submit\" value=\"上传\"/>" //
                        + "</form>";
            }
        }

        // 本机目录请求, 走dubbo添加写操作框框
        BudoGraphService budoGraphService = buildBudoGraphService(ip, port, applicationContext);
        String serviceDubboDruidStat = budoGraphService.serviceDubboDruidStat(api);
        serviceDubboDruidStat = serviceDubboDruidStat.replace(FileExplorerUtil.AFTER_FILE_INDEX, afterFileIndex);
        return new ByteArrayInputStream(serviceDubboDruidStat.getBytes());
    }

    public static BudoGraphService buildBudoGraphService(String ip, Integer port, ApplicationContext applicationContext) {
        // 本地 进程内
        if (null != applicationContext && ("127.0.0.1:0".equalsIgnoreCase(ip + ":" + port) || "null:null".equalsIgnoreCase(ip + ":" + port))) { // 连接当前实例
            BudoGraphService budoGraphService = SpringUtil.getBean(applicationContext, BudoGraphService.class);
            if (null != budoGraphService) {
                return budoGraphService;
            }

            log.warn("#293 new BudoGraphServiceImpl, ip=" + ip + ", port=" + port + ", applicationContext=" + applicationContext);
            return new SpringBudoGraphServiceImpl(applicationContext);
        }

        // HTTP 远程
        BudoApplication budoApplication = BudoApplicationRegistryUtil.findBudoApplication(ip, port);
        if (null != budoApplication) {
            return new HttpRemoteGraphServiceImpl(budoApplication);
        }

        // Dubbo 远程
        String interfaceName = BudoGraphService.class.getName();
        String url = "dubbo://" + ip + ":" + port + "/" + interfaceName;

        
        // init applicationConfig
        Object applicationConfig = Dubbo.applicationConfig("BudoGraphViewUtil");

        // init ReferenceBuilder
        ReferenceBuilder referenceBuilder = new ReferenceBuilder();

        referenceBuilder.setApplicationConfig(applicationConfig);
        Object reference = referenceBuilder.setUrl(url) //
                .setInterface(interfaceName) //
                .setGeneric(false) //
                .setTimeout(REFERENCE_TIMEOUT) // 请求超时时间
                .getReference();

        return (BudoGraphService) reference;
    }

    public static void writeStaticResponse(ApplicationContext applicationContext, HttpServletRequest request, HttpServletResponse response) {
        String requestURI = request.getRequestURI();
        if (StringUtil.contains(requestURI, "/_static_/")) {
            if (StringUtil.endsWith(requestURI, ".js")) {
                response.setContentType(Constant.ContentType.TEXT_JAVASCRIPT);
            } else if (StringUtil.endsWith(requestURI, ".css")) {
                response.setContentType(Constant.ContentType.TEXT_CSS);
            }

            String path = "META-INF/resources" + requestURI.replace(request.getContextPath(), "");
            String inputString = ResourceUtil.classPathResourceInputStreamToString(path);
            IoUtil.write(inputString, ResponseUtil.getOutputStream(response));
            return;
        }

        if (StringUtil.endsWith(requestURI, ".html")) {
            response.setContentType(Constant.ContentType.TEXT_HTML);

            String file = requestURI.substring(requestURI.lastIndexOf('/'));
            writeStaticResponse(applicationContext, request, response, file);
            return;
        }

        if (StringUtil.endsWith(requestURI, ".js")) {
            response.setContentType(Constant.ContentType.TEXT_JAVASCRIPT);

            String file = requestURI.substring(requestURI.lastIndexOf('/'));
            writeStaticResponse(applicationContext, request, response, "/js" + file);
            return;
        }

        if (StringUtil.endsWith(requestURI, ".css")) {
            response.setContentType(Constant.ContentType.TEXT_CSS);

            String file = requestURI.substring(requestURI.lastIndexOf('/'));
            writeStaticResponse(applicationContext, request, response, "/css" + file);
            return;
        }

        log.error("#270 writeStaticResponse else, requestURI=" + requestURI);
    }

    private static void writeStaticResponse(ApplicationContext applicationContext, HttpServletRequest request, HttpServletResponse response, String file) {
        String requestURI = request.getRequestURI();

        String filePath = Constant.DRUID_TEMPLATE_PATH + file;
        filePath = filePath.replace('/', File.separatorChar);
        filePath = filePath.replace('\\', File.separatorChar);

        String substring = requestURI.substring(2);
        int a = substring.indexOf('/');
        int b = substring.indexOf('/', a + 1);
        int c = substring.indexOf('/', b + 1);

        if (StringUtil.endsWith(requestURI, "index.html") //
                && (a < 0 || b < 0 || c < 0 || b <= a || c <= b)) {
            String location = request.getContextPath() + Constant.URL_ROOT + "?" + requestURI;
            ResponseUtil.sendRedirect(response, location);
            return;
        }

        String inputString = ResourceUtil.classPathResourceInputStreamToString(filePath);
        if (StringUtil.endWith(requestURI, ".js")) {
            IoUtil.write(inputString, ResponseUtil.getOutputStream(response));
            return;
        }

        Map<String, Object> map = getDubboProviderMap_1(applicationContext, request);
        FreemarkerUtil.freemarkerRender(inputString, map, ResponseUtil.getOutputStream(response));
    }

    private static Map<String, Object> getDubboProviderMap_1(ApplicationContext applicationContext, HttpServletRequest request) {
        if (!ReflectUtil.hasClass(Constant.DUBBO_NOTIFY_LISTENER)) { // 未引入 dubbo 依赖
            return MapUtil.stringObjectMap("request", request);
        }

        Object registryService = DubboProviderUtil.getRegistryService(applicationContext);
        if (null == registryService) {
            log.error("#390 no registryService find ... ");
            return MapUtil.stringObjectMap("request", request);
        }

        String ip = getIp(request);
        Integer port = getPort(request);
        DubboProvider dubboProvider = DubboProviderUtil.getDubboProvider(registryService, ip, port, applicationContext);

        return MapUtil.stringObjectMap("dubboProvider", dubboProvider, //
                "request", request);
    }

    public static void writeStaticResponse_2(ApplicationContext applicationContext, HttpServletRequest request, HttpServletResponse response, String path) {
        Map<String, Object> map_1 = getDubboProviderMap_1(applicationContext, request);
        String inputString = ResourceUtil.classPathResourceInputStreamToString(path);

        FreemarkerUtil.freemarkerRender(inputString, map_1, ResponseUtil.getOutputStream(response));
        ResponseUtil.flushBuffer(response);
    }

    public static void writeIndexResponse_FM(ApplicationContext applicationContext, HttpServletRequest request, HttpServletResponse response) {
        Collection<DubboProvider> providerList = DubboProviderUtil.getDubboProviders_1(applicationContext, false, true);
        log.info("#89 writeIndexResponse_FM, providerList.size=" + ListUtil.size(providerList));

        Map<String, String> parameters = QueryStringUtil.queryStringToStringMap(request.getRequestURI() + "?" + request.getQueryString());
        Map<String, Object> map = MapUtil.stringObjectMap("dubboProviders", providerList, "RequestParameters", parameters, "request", request);

        String inputString = ResourceUtil.classPathResourceInputStreamToString(DUBBO_DRUID_FM_DUBBO_DRUID_INDEX);
        FreemarkerUtil.freemarkerRender(inputString, map, ResponseUtil.getOutputStream(response));
        ResponseUtil.flushBuffer(response);
    }

    public static void writeApplication(ApplicationContext applicationContext, HttpServletResponse response) {
        List<Map<String, Object>> applications = new ArrayList<Map<String, Object>>();

        Collection<DubboProvider> dubboProviders = DubboProviderUtil.getDubboProviders_1(applicationContext, false, true);
        if (null != dubboProviders) {
            for (DubboProvider dubboProvider : dubboProviders) {
                Map<String, Object> map = MapUtil.stringObjectMap("name", dubboProvider.getApplicationName(), //
                        "processName", dubboProvider.getProcessName(), //
                        "startTime", dubboProvider.getStartTime(), //
                        "host", dubboProvider.getHost(), //
                        "port", dubboProvider.getPort());
                applications.add(map);
            }
        }

        log.info("#403 writeApplication, dubboProviders.size=" + ListUtil.size(dubboProviders));

        response.setContentType(Constant.ContentType.APPLICATION_JSON);
        ResponseUtil.writeToResponse(response, JSONUtils.toJSONString(applications));
    }
}
